﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using VideoReflector.Models;
using VideoReflector.Controllers;
using nVLC.Declarations;
using nVLC.Declarations.Media;
using nVLC.Declarations.Players;
using nVLC.Declarations.Events;
using nVLC.Implementation;

namespace VideoReflector.Video
{
    /// <summary>
    /// Implementation of IVideoRecorder using libvlc to allow capture from a video device to file
    /// </summary>
    public class VlcVideoRecorder : NotifyingObject, IVideoRecorder
    {
        #region Private fields

        private string _outputPath;
        private AudioVideoSettings _avSettings;
        private string _inputModule;
        private IMediaPlayerFactory _vlcMediaFactory;
        private IVideoPlayer _vlcPlayer;
        private IMedia _vlcMedia;
        private DispatcherTimer _timer;
        private TimeSpan _elapsedTime;
        private bool _haveStartedRecording;

        #endregion // Private fields

        #region Constructor

        /// <summary>
        /// Initialises a new libvlc based video recorder object with the specified output file and settings
        /// </summary>
        /// <param name="outputPath"> Path to output recorded data to </param>
        /// <param name="avSettings"> Audio-video configuration options for recording </param>
        /// <param name="displayContainer"> The UI element which will hold the display output </param>
        /// <param name="inputModule"> The VLC module to use as input - e.g. dshow (DirectShow, default), 
        /// v4l2 (Video4Linux2) </param>
        public VlcVideoRecorder (string outputPath, AudioVideoSettings avSettings, 
                                 Panel displayContainer, string inputModule = "dshow") 
        {
            _avSettings = avSettings;
            _inputModule = inputModule;
            _haveStartedRecording = false;
            // set output path
            _outputPath = outputPath;
            // create nVLC objects
            _vlcMediaFactory = new MediaPlayerFactory();
            _vlcPlayer = _vlcMediaFactory.CreatePlayer<IVideoPlayer>();
            // use DirectShow source for media object
            _vlcMedia = _vlcMediaFactory.CreateMedia<IMedia>(inputModule + "://");
            // add options generated from audio-video settings
            _vlcMedia.AddOptions(GenerateOptionsList());
            _vlcPlayer.Open(_vlcMedia);
            // initialise elapsed time counter
            _elapsedTime = new TimeSpan(0,0,0);
            // create timer to increment elapsed time counter and set interval to one second
            _timer = new DispatcherTimer();
            _timer.Interval = new TimeSpan(0, 0, 1);
            _timer.Tick += new EventHandler(_timer_Tick);
            // associate player playing / stop / pause events with timer and IsRecording change notificaiton
            _vlcPlayer.Events.PlayerPlaying += Events_PlayerPlaying;
            _vlcPlayer.Events.PlayerStopped += Events_PlayerStopped;
            _vlcPlayer.Events.PlayerPaused += Events_PlayerStopped;
            // set volume to zero to prevent audio feedback
            _vlcPlayer.Volume = 0;
            // set up video preview display
            var res = _avSettings.VideoOutputFormat.Resolution;
            VlcDisplayController.SetUpDisplay(_vlcPlayer, displayContainer, res.Height, res.Width);
        }

        #endregion // Constructor

        #region Event handlers

        /// <summary>
        /// Elapsed time update handler
        /// </summary>
        private void _timer_Tick(object sender, EventArgs e)
        {
            TimeSpan secondInterval = new TimeSpan(0, 0, 1);
            _elapsedTime = _elapsedTime.Add(secondInterval);
            NotifyPropertyChanged("ElapsedTime");
        }

        private void Events_PlayerStopped(object sender, EventArgs e)
        {
            _timer.Stop();
            NotifyPropertyChanged("IsRecording");
        }

        private void Events_PlayerPlaying(object sender, EventArgs e)
        {
            _timer.Start();
            NotifyPropertyChanged("IsRecording");
        }

        #endregion // Event handlers

        #region Helpers

        /// <summary>
        /// Generate a list of options strings to pass to VLC media object corresponding to specified audio-video settings
        /// </summary>
        protected List<string> GenerateOptionsList()
        {
            List<string> optionsList = new List<string>();
            // video input device
            optionsList.Add(String.Format(":{0}-vdev={1}", _inputModule, _avSettings.VideoDeviceName));
            // audio input device
            optionsList.Add(String.Format(":{0}-adev={1}", _inputModule, _avSettings.AudioDeviceName));
            // video input resolution
            optionsList.Add(String.Format(":{0}-size={1}", _inputModule, 
                _avSettings.VideoOutputFormat.Resolution.ToString()));
            // video input frame rate
            optionsList.Add(String.Format(":{0}-fps={1}", _inputModule, _avSettings.VideoOutputFormat.FrameRate));
            // output configuration
            optionsList.Add(GenerateVlcOutputString());
            return optionsList;
        }

        /// <summary>
        /// Generate a VLC output options string corresponding to the specified configuration options
        /// </summary>
        protected virtual string GenerateVlcOutputString()
        {
            // video codec, frame-rate, bit-rate and scale (fixed)
            string outputOptionsVideo = String.Format(":sout=#transcode{{vcodec={0},fps={1},vb={2},scale=1,",
                _avSettings.VideoOutputFormat.Codec.shortName, _avSettings.VideoOutputFormat.FrameRate, 
                _avSettings.VideoOutputFormat.BitRate);
            // audio codec, bit-rate, number of channels and sample-rate
            string outputOptionsAudio = String.Format("acodec={0},ab={1},channels={2},samplerate={3}}}", 
                _avSettings.AudioOutputFormat.Codec.shortName, _avSettings.AudioOutputFormat.BitRate, 
                _avSettings.AudioOutputFormat.Channels, _avSettings.AudioOutputFormat.SampleRate);
            // set output destination based on whether output is being cloned to display or not
            string outputDestinationPattern = _avSettings.CloneToScreen
                ? ":duplicate{{dst=file{{mux={0},dst={1}}},dst=display}}" 
                : " :file{{mux={0},dst={1}}}";
            string outputOptionsDestination = String.Format(outputDestinationPattern,  _avSettings.Container, _outputPath);
            // coantenate option string components
            return outputOptionsVideo + outputOptionsAudio + outputOptionsDestination;
        }

        #endregion // Helpers

        #region IDisposable implementations

        /// <summary>
        /// Dispose of nVLC objects which reference unmanaged resources
        /// </summary>
        public void Dispose()
        {
            _vlcMedia.Dispose();
            _vlcPlayer.Dispose();
            _vlcMediaFactory.Dispose();
            _timer.Stop();
        }

        #endregion // IDisposable implementations

        #region IVideoRecorder implementations

        public void Start()
        {
            _vlcPlayer.Play();
            _haveStartedRecording = true;  // extra flag in addition to Player.IsPlaying
                                           // as delay in player startup means immediately after
                                           // start executed IsRecording != true
        }

        public void Pause()
        {
            _vlcPlayer.Pause();
        }

        public void Stop()
        {
            _vlcPlayer.Stop();
            _haveStartedRecording = false;
        }

        public bool IsRecording
        {
            get
            {
                return _vlcPlayer.IsPlaying || _haveStartedRecording;
            }
        }

        public TimeSpan ElapsedTime
        {
            get
            {
                return _elapsedTime;
            }
        }

        #endregion // IVideoRecorder implementations
    }
}
